package com.config;

import com.domain.CustomUser;
import com.domain.Status;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.memory.UserAttribute;
import org.springframework.security.core.userdetails.memory.UserAttributeEditor;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.util.Assert;

import java.util.*;

public class CustomInMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
    protected final Log logger = LogFactory.getLog(getClass());

    private final Map<String, CustomUser> users = new HashMap<>();

    private AuthenticationManager authenticationManager;

    public CustomInMemoryUserDetailsManager() {
    }

    public CustomInMemoryUserDetailsManager(Collection<CustomUser> users) {
        for (UserDetails user : users) {
            createUser(user);
        }
    }

    public CustomInMemoryUserDetailsManager(CustomUser... users) {
        for (UserDetails user : users) {
            createUser(user);
        }
    }

    public CustomInMemoryUserDetailsManager(Properties users) {
        Enumeration<?> names = users.propertyNames();
        UserAttributeEditor editor = new UserAttributeEditor();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            editor.setAsText(users.getProperty(name));
            UserAttribute attr = (UserAttribute) editor.getValue();
            Assert.notNull(attr,
                    () -> "The entry with username '" + name + "' could not be converted to an UserDetails");
            createUser(createUserDetails(name, attr));
        }
    }

    private User createUserDetails(String name, UserAttribute attr) {
        return new User(name, attr.getPassword(), attr.isEnabled(), true, true, true, attr.getAuthorities());
    }

    @Override
    public void createUser(UserDetails user) {
        // CustomUser.withUsername("user").password("{bcrypt}$2a$10$Nl8zvMdhS0l22hBR.hAsNuQeMWH0.dgVRC/4.a0ny37SiI2Hbc4iW").roles("USER").build()
        Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
        // org.springframework.security.core.userdetails.User cannot be cast to com.domain.CustomUser
        this.users.put(user.getUsername().toLowerCase(), (CustomUser)user);
    }

    @Override
    public void deleteUser(String username) {
        this.users.remove(username.toLowerCase());
    }

    @Override
    public void updateUser(UserDetails user) {
        Assert.isTrue(userExists(user.getUsername()), "user should exist");
        this.users.put(user.getUsername().toLowerCase(),  (CustomUser)user);
    }

    @Override
    public boolean userExists(String username) {
        return this.users.containsKey(username.toLowerCase());
    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {
        Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
        if (currentUser == null) {
            // This would indicate bad coding somewhere
            throw new AccessDeniedException(
                    "Can't change password as no Authentication object found in context " + "for current user.");
        }
        String username = currentUser.getName();
        this.logger.debug(LogMessage.format("Changing password for user '%s'", username));
        // If an authentication manager has been set, re-authenticate the user with the
        // supplied password.
        if (this.authenticationManager != null) {
            this.logger.debug(LogMessage.format("Reauthenticating user '%s' for password change request.", username));
            this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, oldPassword));
        } else {
            this.logger.debug("No authentication manager set. Password won't be re-checked.");
        }
        CustomUser user = this.users.get(username);
        Assert.state(user != null, "Current user doesn't exist in database.");
        user.setPassword(newPassword);
    }

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        String username = user.getUsername();
        CustomUser mutableUser = this.users.get(username.toLowerCase());
        mutableUser.setPassword(newPassword);
        return mutableUser;
    }

    @Override
    public CustomUser loadUserByUsername(String username) throws UsernameNotFoundException {
        CustomUser user = this.users.get(username.toLowerCase());
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        String password=user.getPassword();
        if(user.getStatus()== Status.WECHAT){
            // com.config.MutableUser cannot be cast to com.domain.CustomUser
            CustomUser user1 = (CustomUser) user;
            password = "{noop}"+user1.getOpenId();
        }
        return new CustomUser(user.getUsername(), password, user.isEnabled(), user.isAccountNonExpired(),
                user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
    }

    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
}